﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Text.RegularExpressions;
using System.Threading;


struct Coordinate
{
    public int row;
    public List<int> cols;

    public Coordinate(int r, List<int> c) : this()
    {
        this.row = r;
        this.cols = new List<int>(c);
    }
}


class PairsGame : Game
{

    private int height;
    private int width;
    private string[,] table;
    private string[,] tableForShow;
    private bool[,] revealedCells;
    private List<Coordinate> coordinates;
    private int revealedTime;
    private int gameTime;
    private int numberOfTurns;
    private bool continuePooling = true; //used to indicate wether to continue or not pooling for user input
    Random xRand;
    Random yRand;
    //private int

    /// <summary>
    /// Determines the dimensions of the table depending on the set difficulty
    /// </summary>
    private void InitSize()
    {
        if (this.difficulty <= 2) //difficulty up to 2 => table with 10 cells - 5 pairs
        {
            this.height = 2;
            this.width = 5;
            this.revealedTime=3000; //3secs
            this.gameTime = 40000;
            this.numberOfTurns = 7;
        }
        else if (this.difficulty <= 5)//difficulty from 3 to 5 => table with 16 cells - 8 pairs
        {
            this.height = 4;
            this.width = 4;
            this.revealedTime=2500; //2.5secs
            this.gameTime = 30000;
            this.numberOfTurns = 7;
        }
        else if (this.difficulty <= 8) //difficulty from 6 to 8 => table with 20 cells - 10 pairs
        {
            this.height = 4;
            this.width = 5;
            this.revealedTime=2000; //2secs
            this.gameTime = 25000;
            this.numberOfTurns = 6;
        }
        else //difficulty 9 => table with 52 cells - 26 pairs
        {
            this.height = 4;
            this.width = 13;
            this.revealedTime=1000; //1sec
            this.gameTime = 20000;
            this.numberOfTurns = 5;
        }
    }

    /// <summary>
    /// Initialize the possible coordinates
    /// </summary>
    private void InitCoordinateLists()
    {
        this.coordinates = new List<Coordinate>();
        List<int> cols = new List<int>();

        //initialize the list representing the possible col coordinates
        for (int i = 0; i < this.width; i++)
        {
            cols.Add(i);
        }

        //initialize the list containing the possible row coordinates
        //with their respective col coordinates
        for (int i = 0; i < this.height; i++)
        {
            this.coordinates.Add(new Coordinate(i, cols));
        }
    }

    private void AddLetter(char c)
    {
        Coordinate coord = this.coordinates[yRand.Next(0, coordinates.Count)];
        int row = coord.row;
        int col = coord.cols[xRand.Next(0, coord.cols.Count)];

        this.table[row, col] = c.ToString();
        this.tableForShow[row, col] = "?";//populate the table which the user will be seing

        coord.cols.Remove(col);
        if (coord.cols.Count == 0)
        {
            this.coordinates.Remove(coord);
        }
    }

    private void PopulateTable()
    {
        int pairsCount = (this.width * this.height)/2;
        for (int i = 1; i <= pairsCount; i++)
        {
            char letter = (char)(i + 64); //the letter for the pair
            this.AddLetter(letter);
            this.AddLetter(letter);
        }
    }

    /// <summary>
    /// The primary constructor for this game
    /// </summary>
    /// <param name="diff">The difficulty level - ranges from 0 to 9 included</param>
    public PairsGame(int diff)
        :base(diff)
    {
        this.InitSize();
        this.InitCoordinateLists();
        table = new string[this.height, this.width];
        this.tableForShow = new string[this.height, this.width];
        this.revealedCells = new bool[this.height, this.width];
        this.xRand = new Random();
        this.yRand = new Random();
        this.PopulateTable();
    }


    private void VisualizeTable(int mode, int c1r=-1, int c1c=-1, int c2r=-1, int c2c=-1)
    {
        string[,] tab;
        if (mode == 0)
        {
            tab = this.table;
        }
        else
        {
            tab = this.tableForShow;
        }

        for (int r = 0; r < this.height; r++)
        {
            for (int c = 0; c < this.width; c++)
            {
                //in mode 1
                //if we are located on one of the opened cells, visualize the cell
                //contents instead of ?
                if (r==c1r && c==c1c ||r==c2r && c==c2c)
                {
                    Console.Write(this.table[r, c].PadLeft(3));
                }
                else
                {
                    Console.Write(tab[r, c].PadLeft(3));
                }
               
            }
            Console.WriteLine();
        }
    }

    /// <summary>
    /// Validates the format of the input
    /// </summary>
    /// <param name="input">The user inputed string, specifyint the coordinates of 2 cells to be opened.</param>
    private void ValidateInput(string input)
    {
        string re1 = "(\\d+)";	// Integer Number 1
        string re2 = "(\\s+)";	// White Space 1
        string re3 = "(\\d+)";	// Integer Number 2
        string re4 = ".*?";	// Non-greedy match on filler
        string re5 = "(&)";	// Any Single Character 1
        string re6 = ".*?";	// Non-greedy match on filler
        string re7 = "(\\d+)";	// Integer Number 3
        string re8 = "(\\s+)";	// White Space 2
        string re9 = "(\\d+)";	// Integer Number 4

        Regex r = new Regex(re1 + re2 + re3 + re4 + re5 + re6 + re7 + re8 + re9, RegexOptions.IgnoreCase | RegexOptions.Singleline);
        Match m = r.Match(input);
        if (m.Success==false)
        {
            throw new InvalidPlayerInputException("Input does not meet the specified format");
        }
    }

    private void ProcessInput(string input)
    {
        string[] coordinates = input.Split(new string[] {" & "}, StringSplitOptions.None);
        string[] cell1 = coordinates[0].Split(' ');
        string[] cell2 = coordinates[1].Split(' ');
        int c1r = int.Parse(cell1[0]);
        int c1c = int.Parse(cell1[1]);
        int c2r = int.Parse(cell2[0]);
        int c2c = int.Parse(cell2[1]);

        //TODO: check if the cells selected by the user have not beeen already revealed
        //and display message if they have been

        //cells have not been revealed and its not one and the same cell
        if (this.revealedCells[c1r, c1c] == false && this.revealedCells[c2r, c2c] == false && (c1r!=c2r || c1c!=c2c))
        {
            //the user guessed right (the 2 cells have the same value)
            if (this.table[c1r, c1c] == this.table[c2r, c2c])
            {
                this.result = result + 100 * (this.difficulty + 1); //add pointZ
                //mark cells as revealed
                this.revealedCells[c1r, c1c] = true;
                this.revealedCells[c2r, c2c] = true;

                //modify the table which is shown to the user
                this.tableForShow[c1r, c1c] = this.table[c1r, c1c];
                this.tableForShow[c2r, c2c] = this.table[c2r, c2c];
                Console.Clear();
                this.VisualizeTable(1);
            }
            else
            {
                Console.Clear();
                this.VisualizeTable(1, c1r, c1c, c2r, c2c);
                Console.WriteLine("Your guess was wrong!");
                Thread.Sleep(4000);
                Console.Clear();
                this.VisualizeTable(1);
            }
        }
        else//those cells have already been opened
        {
            Console.WriteLine("Those cells have already been revealed, or you are trying to select the same cell twice!");
        }
        
    }

    private void WaitForInput()
    {
        int turnsPassed = 0;
        
        Timer tmr = new Timer(EndGameCaller, null, this.gameTime, Timeout.Infinite);
        while (this.continuePooling)
        {
            Console.WriteLine("Please input coordinates of two cells to be opened. Please follow the format: 'row col & row col'");
            string input = Console.ReadLine();
            if (this.continuePooling == true)
            {
                this.ValidateInput(input);
                turnsPassed++;
                if (turnsPassed == this.numberOfTurns)
                {
                    this.EndGame();
                    break;
                }
                this.ProcessInput(input);
            }
        }
    }

    public override void StartGame()
    {
        this.VisualizeTable(0);
        Thread.Sleep(this.revealedTime);
        Console.Clear();
        this.VisualizeTable(1);
        try
        {
            this.WaitForInput();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }


    private void EndGameCaller(Object o)
    {
        this.EndGame();
    }

    //the object o thing is just so that the method can be passed to the timer
    public override void EndGame()
    {
        Console.Clear();
        this.continuePooling=false;
        Console.WriteLine("Your score is: " + this.result);
        Console.WriteLine("Press any key to continue.");
        Console.ReadKey();
    }
}

